/*
 * Copyright 2010 Maxim Sokhatsky <maxim.sokhatsky@gmail.com>
 * All rights reserved. Distributed under the terms of the MIT license.
 */
 
#include "JabberProtocol.h"
#include <QtNetwork/QSslSocket>

#define DEBUG 

class SessionDispatcher : public QThread
{
public:
    SessionDispatcher(JabberProtocol *jabber) {
        this->jabber = jabber;
    }
protected:
    void run() {
        QVariant *msg = new QVariant("");//new BMessage(PORT_TALKER_DATA);

        while (true)
        {
                jabber->read_queue->acquire();

                jabber->ReceiveData(msg);
                jabber->ReceivedMessageHandler(msg);

                jabber->read_queue->release();

                QThread::sleep(1000);
                //snooze(1000);
        }

        //return 0;
    }
private:
    JabberProtocol *jabber;
};

static int zeroReceived = 0;
static int separatorReceived = 0;

JabberProtocol::JabberProtocol()
{
	_storage_supported = true;
        //logged = create_sem(1, "logged");
        logged = new QSystemSemaphore(QString("logged"), 1, QSystemSemaphore::Create);
        //read_queue = create_sem(1, "read_queue");
        read_queue = new QSystemSemaphore(QString("read_queue"), 1, QSystemSemaphore::Create);
        //xml_reader_lock = create_sem(1, "xml_reader");
        xml_reader_lock = new QSystemSemaphore(QString("xml_reader"), 1, QSystemSemaphore::Create);

        reciever_thread =  new SessionDispatcher(this);//spawn_thread(SessionDispatcher, "xmpp_receiver", B_LOW_PRIORITY, this);
        reciever_thread->setPriority(QThread::LowPriority);
        reciever_thread->start();

        socketAdapter = new QSslSocket();
}

JabberProtocol::~JabberProtocol()
{
}

void
JabberProtocol::SetConnection(QString h, int p, bool s)
{
	host = h;
	port = p;
	secure = s;
}

void
JabberProtocol::SetCredentials(QString u, QString d, QString p)
{
	domain = d;
	user = u;
	pass = p;
}

void
JabberProtocol::LockXMLReader() {
    xml_reader_lock->acquire();
}

void
JabberProtocol::UnlockXMLReader() {
    xml_reader_lock->release();
}

void 
JabberProtocol::LogOn() 
{
	if (BeginSession())
	{
                logged->acquire();
                // TODO
                //reciever_thread->
                //resume_thread(reciever_thread); //resume trade
	}
	else
	{
                qDebug(QObject::tr("Cannot connect to %s:%i.").arg(host, port).toAscii());
                // TODO
                //ModalAlertFactory::Alert(buffer, "Sorry", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
	
		Disconnect();
	}
}

void
JabberProtocol::SetStatus(QString status, QString message)
{
        logged->acquire();
        logged->release();
	
        QString xml;
        xml.append("<presence from='");
        xml.append(jid);
        xml.append("'><show>");
        xml.append(status);
        xml.append("</show><status>");
        xml.append(message);
        xml.append("</status></presence>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::OnStartTag(XMLEntity *entity)
{
}

void
JabberProtocol::OnEndTag(XMLEntity *entity)
{
        OnTag(entity);
}

void
JabberProtocol::OnEndEntity(XMLEntity *entity)
{

}

void
JabberProtocol::OnTag(XMLEntity *entity)
{
	char buffer[4096]; // general buffer space
	static int seen_streams = 0;

	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "iq"))
	{
		
		// handle roster retrival
		if (entity->Child("query") && entity->Child("query")->Attribute("xmlns") &&
			!strcasecmp(entity->Child("query")->Attribute("xmlns"),"jabber:iq:roster"))
		{
			ParseRosterList(entity);
			return;
		}
		
		// handle session retrival
		if (entity->Attribute("id") && entity->Attribute("type") &&
			!strcasecmp(entity->Attribute("type"), "result") &&
			!strcasecmp(entity->Attribute("id"), "sess_1"))
		{
                        logged->release();
		
                        //TODO //mainWindow->Lock();
                        //mainWindow->PostMessage(JAB_LOGGED_IN);
                        //mainWindow->Unlock();
			
			return;
		}
		
		// handle binding retrival
		if (entity->Attribute("id") && entity->Attribute("type") &&
			!strcasecmp(entity->Attribute("type"), "result") &&
			!strcasecmp(entity->Attribute("id"), "bind_0"))
		{
                        jid = QString(entity->Child("bind")->Child("jid")->Data());

                        qDebug()<< "JID: " << jid;//fprintf(stderr, "JID: %s.\n", jid.String());

			Session();
			
			return;
		}
		
		if (entity->Attribute("type") && entity->Child("query") &&
			!strcasecmp(entity->Attribute("type"), "result") &&
			entity->Child("query", "xmlns", "jabber:iq:register"))
		{
			Authorize();
			
			return;
		}
		
		if (entity->Attribute("type") && entity->Attribute("id") &&
			!strcasecmp(entity->Attribute("type"), "result") &&
			!strcasecmp(entity->Attribute("id"), "request_room_info"))
		{
                        //TODO //BMessage msg(JAB_PREFERENCES_DATAFORM);
                        //msg.AddPointer("XMLEntity", entity);
                        //MessageRepeater::Instance()->PostMessage(&msg);
			
			return;
		}
		
		if (entity->Attribute("type") && entity->Attribute("id") &&
			!strcasecmp(entity->Attribute("type"), "error"))
		{
			if (!strcasecmp(entity->Attribute("id"), "storage_request"))
			{
				_storage_supported = false;
				return;
			}
			else if (!strcasecmp(entity->Attribute("id"), "save_conferences"))
			{
				_storage_supported = false;
				sprintf(buffer, "Storage XEP-0049 is not supported on server. Cannot save conferences.\n\nNext time will try save to roster.");
                                //ModalAlertFactory::Alert(buffer, "Pity", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
				return;
			}
			else if (!strcasecmp(entity->Attribute("id"), "request_room_info"))
			{
				
				if (entity->Child("error") &&
					entity->Child("error")->Child("text") &&
					entity->Child("error")->Attribute("code"))
					sprintf(buffer, "Error %s:\n\n%s", entity->Child("error")->Attribute("code"),
							entity->Child("error")->Child("text")->Data());
				else
					sprintf(buffer, "Error %s", entity->Child("error")->Attribute("code"));
			
                                //ModalAlertFactory::Alert(buffer, "OK", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
			
				return;
			}
				
			if (entity->Child("error") &&
				entity->Child("error")->Child("text") &&
				entity->Child("error")->Attribute("code"))
				sprintf(buffer, "Error %s:\n\n%s", entity->Child("error")->Attribute("code"),
					entity->Child("error")->Child("text")->Data());
			else
				sprintf(buffer, "Error %s", entity->Child("error")->Attribute("code"));
			
                        //ModalAlertFactory::Alert(buffer, "OK", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
			
			Disconnect();
			
			return;
			
		}
		
		if (entity->Attribute("id") && entity->Child("query") && entity->Child("query")->Attribute("xmlns") &&
			entity->Attribute("type") &&
			!strcasecmp(entity->Attribute("id"), "storage_request") &&
			!strcasecmp(entity->Attribute("type"), "result") &&
			!strcasecmp(entity->Child("query")->Attribute("xmlns"), "jabber:iq:private"))
		{
			if (entity->Child("query")->Child("storage"))
				ParseStorage(entity->Child("query")->Child("storage"));
				
			return;
		}
		
		if (entity->Attribute("type") && !strcasecmp(entity->Attribute("type"), "get"))
		{
                        QString iq_from;
                        QString iq_id;
			
			if (entity->Attribute("from")) {
                                iq_from = QString(entity->Attribute("from"));
			}
			
			if (entity->Attribute("id")) {
                                iq_id = QString(entity->Attribute("id"));
			}
			
			// handle version request
			XMLEntity *query = entity->Child("query");
			if (query && query->Attribute("xmlns")) {
				if (!strcasecmp(query->Attribute("xmlns"), "jabber:iq:version")) {
					ProcessVersionRequest(iq_id, iq_from);
				}
			}
			
			// handle version request
			query = entity->Child("ping");
			if (query && query->Attribute("xmlns")) {
				if (!strcasecmp(query->Attribute("xmlns"), "urn:xmpp:ping"))
				{
					Pong(iq_id, iq_from);
				}
			}
			
			return;
		}
		
		fprintf(stderr, "Unknown IQ message.\n");
		return;
	}
	
	// handle authorization success
	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "success"))
	{
		InitSession();
		
		return;
		
	}
	
	// handle presence messages
	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "presence"))
	{
		ProcessPresence(entity);
		
		return;
	}
	
	// handle stream error
	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "stream:error")) {
		sprintf(buffer, "An stream error has occurred.");
                //ModalAlertFactory::Alert(buffer, "Sorry", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
		
		Disconnect();
		
		return;
	}
	
	// handle stream error
	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "stream:features"))
	{
                //mainWindow->Lock();
                int wantRegister = 0;//mainWindow->_login_new_account->Value();
                //mainWindow->Unlock();
		
                if (wantRegister == 0/*B_CONTROL_ON*/)
		{
			if (entity->Child("register"))
			{
                                //mainWindow->Lock();
                                //mainWindow->_login_new_account->SetValue(B_CONTROL_OFF);
                                //mainWindow->Unlock();
				
				SendUserRegistration(user, pass, "haiku");
			} else
			{
				sprintf(buffer, "Registration not supported on this server.");
                                //ModalAlertFactory::Alert(buffer, "Sorry", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
				
				Disconnect();
			}
		}
		else if (entity->Child("mechanisms"))
			Authorize();
		else if (entity->Child("bind"))
			Bind();
		else if (entity->Child("session"))
			Session();
			
		return;
		
	}
	
	// handle failures
	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "failure")) {
		if (entity->Child("not-authorized") != NULL)
			sprintf(buffer, "Not authorized failure.");
		else if (entity->Child("invalid-mechanism") != NULL)
			sprintf(buffer, "Invalid mechanism failure.");
		else if (entity->Child("invalid-authzid") != NULL)
			sprintf(buffer, "Invalid authorization Id.");
		else
			sprintf(buffer, "An failure occured.");
			
                //ModalAlertFactory::Alert(buffer, "Sorry", NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
		
		Disconnect();
		
		return;
	}
	
	// handle disconnection
	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "stream:stream"))
	{
		++seen_streams;
		if (seen_streams % 2 == 1)
			Disconnect();
			
		return;
	}
	
	// handle incoming messages
	if (entity->IsCompleted() && !strcasecmp(entity->Name(), "message"))
	{
		//TalkManager::Instance()->Lock();
                //TalkManager::Instance()->ProcessMessageData(entity);
		//TalkManager::Instance()->Unlock();
		
		return;
	}
	
	//delete entity;
}

void
JabberProtocol::SendUserRegistration(QString username, QString password, QString resource)
{
	XMLEntity   *entity_iq, *entity_query;
	char **atts_iq    = CreateAttributeMemory(4);
	char **atts_query = CreateAttributeMemory(2);

	// assemble attributes;
	strcpy(atts_iq[0], "id");

        strcpy(atts_iq[1], GenerateUniqueID().toUtf8().data());

	strcpy(atts_iq[2], "type");
	strcpy(atts_iq[3], "set");

	strcpy(atts_query[0], "xmlns");
	strcpy(atts_query[1], "jabber:iq:register");

	// construct XML tagset
	entity_iq    = new XMLEntity("iq", (const char **)atts_iq);
	entity_query = new XMLEntity("query", (const char **)atts_query);

	entity_iq->AddChild(entity_query);
	
        entity_query->AddChild("username", NULL, username.toUtf8().data());
        entity_query->AddChild("password", NULL, password.toUtf8().data());
        entity_query->AddChild("resource", NULL, resource.toUtf8().data());

	// log command
	//_iq_map[atts_iq[1]] = NEW_USER;
	
	// send XML command
	char *str = entity_iq->ToString();
        socketAdapter->write(str);
	free(str);

	DestroyAttributeMemory(atts_iq, 4);
	DestroyAttributeMemory(atts_query, 2);
	
	delete entity_iq;
}

void
JabberProtocol::Pong(QString id, QString from)
{
        QString xml = "<iq from='";
        xml.append(jid);
        xml.append("' id='");
        xml.append(id);
        xml.append("' to='");
        xml.append(from);
        xml.append("' type='result'/>");

#ifdef DEBUG	
        fprintf(stderr, "Pong reply to %s.\n", from.data());
#endif
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::SaveConference(UserID *conference)
{
        QString xml ="<iq type='set' id='save_conferences'>"
		"<query xmlns='jabber:iq:private'>"
		"<storage xmlns='storage:bookmarks'>";
		

			
        int count = 2;//mainWindow->_roster->GetConferencesCount();
	bool seen = false;
	
	for (int i = 0; i < count; i++)
	{
                const UserID *conf = NULL;//mainWindow->_roster->GetConference(i);
		
		if (conference && (conf->JabberHandle() == conference->JabberHandle()))
		{
			conf = (const UserID *)conference;
			seen = true;
		}
		
                xml.append("<conference name='");
                xml.append(conf->FriendlyName());
                xml.append("' autojoin='");
                xml.append(conf->Autojoin());
                xml.append("' jid='");
                xml.append(conf->JabberHandle());
                xml.append("'><nick>");
                xml.append(conf->_room_nick);
                xml.append("</nick></conference>");
	
	}
	
	if (!seen && conference)
	{
                xml.append("<conference name='");
                xml.append(conference->FriendlyName());
                xml.append("' autojoin='");
                xml.append(conference->Autojoin());
                xml.append("' jid='");
                xml.append(conference->JabberHandle());
                xml.append("'><nick>");
                xml.append(conference->_room_nick);
                xml.append("</nick></conference>");
	}
	
        xml.append("</storage></query></iq>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::SendUnavailable(QString to, QString status)
{
        QString xml = "<presence to='";
        xml.append(to);
        xml.append("' type='unavailable'>");
        if (status.length() > 0)
	{
                xml.append("<status>");
                xml.append(status);
                xml.append("</status>");
	}
        xml.append("</presence>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::SendAvailable(QString to, QString status)
{
        QString xml = "<presence to='";
        xml.append(to);
        xml.append("' type='available'>");
        if (status.length() > 0)
	{
                xml.append("<status>");
                xml.append(status);
                xml.append("</status>");
	}
        xml.append("</presence>");
	
        socketAdapter->write(xml.toUtf8());
}
void
JabberProtocol::JoinRoom(QString to, QString password)
{
        QString xml = "<presence to='";
        xml.append(to);
        xml.append("'><priority>5</priority>");
        //xml.append("<c xmlns='http://jabber.org/protocol/caps' node='http://dengon.berlios.de/caps' ver='1.0'/>");
        xml.append("<x xmlns='http://jabber.org/protocol/muc'>");
        if (password.length() > 0)
	{
                xml.append("<password>");
                xml.append(password);
                xml.append("</password>");
	}
        xml.append("</x></presence>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::ProcessVersionRequest(QString req_id, QString req_from)
{
	XMLEntity   *entity_iq, *entity_query;
	char **atts_iq    = CreateAttributeMemory(6);
	char **atts_query = CreateAttributeMemory(2);

	// assemble attributes;
	strcpy(atts_iq[0], "id");
        strcpy(atts_iq[1], req_id.toUtf8().data());

	strcpy(atts_iq[2], "to");
        strcpy(atts_iq[3], req_from.toUtf8().data());

	strcpy(atts_iq[4], "type");
	strcpy(atts_iq[5], "result");

	strcpy(atts_query[0], "xmlns");
	strcpy(atts_query[1], "jabber:iq:version");

	// construct XML tagset
	entity_iq    = new XMLEntity("iq", (const char **)atts_iq);
	entity_query = new XMLEntity("query", (const char **)atts_query);

	entity_iq->AddChild(entity_query);
	
	entity_query->AddChild("name", NULL, "Dengon");
        entity_query->AddChild("version", NULL, "1.0 (rev: DENGON_SVNVERSION)"); // TODO version

        QString strVersion("Haiku");
	
        QString os_info;
        /*utsname uname_info;
	if (uname(&uname_info) == 0) {
		os_info = uname_info.sysname;
		long revision = 0;
		if (sscanf(uname_info.version, "r%ld", &revision) == 1) {
			char version[16];
			snprintf(version, sizeof(version), "%ld", revision);
			os_info += " (rev: ";
			os_info += version;
			os_info += ")";
		}
        }*/

        entity_query->AddChild("os", NULL, os_info.toAscii());

	// send XML command
	char *str = entity_iq->ToString();
        socketAdapter->write(str);
	free(str);

	DestroyAttributeMemory(atts_iq, 6);
	DestroyAttributeMemory(atts_query, 2);
	
	delete entity_iq;
}

void
JabberProtocol::SendGroupPresence(QString _group_room, QString _group_username)
{
	XMLEntity             *entity_presence;
	char **atts_presence = CreateAttributeMemory(4);

	// assemble group ID
        QString group_presence = _group_room + "/" + _group_username;
	
	// assemble attributes;
	strcpy(atts_presence[0], "to");
        strcpy(atts_presence[1], group_presence.toUtf8().data());
	
	strcpy(atts_presence[2], "from");
        strcpy(atts_presence[3], jid.toUtf8().data());

	// construct XML tagset
	entity_presence = new XMLEntity("presence", (const char **)atts_presence);
	
	// send XML command
	char *str = entity_presence->ToString();
        socketAdapter->write(str);
	free(str);

	DestroyAttributeMemory(atts_presence, 2);
	
	delete entity_presence;
}

void
JabberProtocol::ProcessPresence(XMLEntity *entity)
{
	

	int num_matches = 0;

	// verify we have a username
	if (entity->Attribute("from"))
	{
		// circumvent groupchat presences
                QString room, server, user;
		
		if (entity->Child("x", "xmlns", "http://jabber.org/protocol/muc#user"))
		{
                        UserID from = UserID(QString(entity->Attribute("from")));
			room = from.JabberUsername();
			server = from.JabberServer();
			user = from.JabberResource();
			fprintf(stderr, "Group Presence in room %s from user %s.\n", 
                                from.JabberHandle(), user);
						
                        //BMessage *msg = new BMessage(JAB_GROUP_CHATTER_ONLINE);
                        //msg->AddString("room", (room + '@' + server));
                        //msg->AddString("server", server);
                        //msg->AddString("username", user);
			
			if (!entity->Attribute("type") || !strcasecmp(entity->Attribute("type"), "available"))
			{
				if (entity->Child("show") && entity->Child("show")->Data())
				{
                                        //msg->AddString("show", entity->Child("show")->Data());
                                } else {
                                        //msg->AddString("show", "online");
                                }

				if (entity->Child("status") && entity->Child("status")->Data())
				{
                                        //msg->AddString("status", entity->Child("status")->Data());
                                } else {
                                        //msg->AddString("status", "");
                                }
				
				if (entity->Child("x")->Child("item") &&
                                    entity->Child("x")->Child("item")->Attribute("role")) {
                                        //msg->AddString("role", entity->Child("x")->Child("item")->Attribute("role"));
                                } else {
                                        //msg->AddString("role", "admin");
                                    }
				if (entity->Child("x")->Child("item") &&
                                    entity->Child("x")->Child("item")->Attribute("affiliation")) {
                                        //msg->AddString("affiliation", entity->Child("x")->Child("item")->Attribute("affiliation"));
                                } else {
                                        //msg->AddString("affiliation", "none");
                                }
                                //msg->what = JAB_GROUP_CHATTER_ONLINE;
			}
			else if (!strcasecmp(entity->Attribute("type"), "unavailable"))
			{
                                //msg->what = JAB_GROUP_CHATTER_OFFLINE;
			}
			
                        //TalkManager::Instance()->Lock();
			
                        //ChatWindow *window = TalkManager::Instance()->FindWindow(from.JabberHandle());
			
                        /*if (window != NULL)
			{
                                fprintf(stderr, "Process group presence %s.\n", window->GetUserID()->JabberHandle());
				
				window->PostMessage(msg);
			}
			else
			{
				fprintf(stderr, "There is no window group presence route to.\n");
                        }*/
			
                        //TalkManager::Instance()->Unlock();
			
			return;
		}		
		
                //JRoster *roster = JRoster::Instance();
		
                //roster->Lock();
		
                /*for (JRoster::ConstRosterIter i = roster->BeginIterator(); i != roster->EndIterator(); ++i)
		{
			UserID *user = NULL;

                        if (!strcasecmp(UserID(entity->Attribute("from")).JabberHandle(),
                                        (*i)->JabberHandle()))
			{
				++num_matches;
				user = *i;
				ProcessUserPresence(user, entity);
                                fprintf(stderr, "Process roster presence %s.\n", user->JabberHandle());
			}
                }*/
		
		if (num_matches == 0)
		{
                        UserID user(QString(entity->Attribute("from")));
                        fprintf(stderr, "Process not in roster presence %s.\n", user.JabberHandle());
			ProcessUserPresence(&user, entity);
		}
			
                //roster->Unlock();
		
                //mainWindow->PostMessage(BLAB_UPDATE_ROSTER);

	}
}

char **
JabberProtocol::CreateAttributeMemory(int num_items)
{
	char **atts;
	
	atts = (char **)malloc((num_items + 2) * sizeof(char *));
	for (int i=0; i<num_items; ++i)
		atts[i] = (char *)malloc(96 * sizeof(char));
	
	atts[num_items] = NULL;
	atts[num_items+1] = NULL;
	
	return atts;
}

void
JabberProtocol::Disconnect()
{
	Reset();
        //JRoster::Instance()->Lock();
        //JRoster::Instance()->RemoveAllUsers();
        //JRoster::Instance()->Unlock();

        //mainWindow->Lock();
        //mainWindow->PostMessage(BLAB_UPDATE_ROSTER);
        //mainWindow->ShowLogin();
        //mainWindow->Unlock();
	
        QString xml = "</stream:stream>";
        socketAdapter->write(xml.toUtf8());
        socketAdapter->close();
	
        logged->release();
        reciever_thread->terminate(); //suspend_thread(reciever_thread);
}

void
JabberProtocol::DestroyAttributeMemory(char **atts, int num_items)
{
	for (int i=0; i<(num_items + 2); ++i) {
		free(atts[i]);
	}
	
	free(atts);
}

void
JabberProtocol::AcceptPresence(QString username) {
	XMLEntity *entity;
	
	char **atts = CreateAttributeMemory(4);
	
	// assemble attributes
	strcpy(atts[0], "to");
        strcpy(atts[1], username.toUtf8().data());
	strcpy(atts[2], "type");
	strcpy(atts[3], "subscribed");

	entity = new XMLEntity("presence", (const char **)atts);

	// send XML command
	char *str = entity->ToString();
        socketAdapter->write(str);
	free(str);
	
	DestroyAttributeMemory(atts, 4);
	delete entity;
}

void
JabberProtocol::RejectPresence(QString username)
{
	XMLEntity *entity;
	
	char **atts = CreateAttributeMemory(4);
	
	// assemble attributes
	strcpy(atts[0], "to");
        strcpy(atts[1], username.toUtf8().data());
	strcpy(atts[2], "type");
	strcpy(atts[3], "unsubscribed");

	entity = new XMLEntity("presence", (const char **)atts);

	// send XML command
	char *str = entity->ToString();
        socketAdapter->write(str);
	free(str);
	
	DestroyAttributeMemory(atts, 4);
	delete entity;
}

QString
JabberProtocol::GenerateUniqueID()
{
	static long counter = 0;
        pid_t pid = 1;//getpid();
        time_t secs = 2;//time(NULL);
	++counter;
	char buffer[100];
	sprintf(buffer, "%lu:%lu:%lu", pid, secs, counter);
        return QString(buffer);
}


void
JabberProtocol::SendSubscriptionRequest(QString username)
{
	XMLEntity *entity;
	
	char **atts = CreateAttributeMemory(6);
	
	// assemble attributes
	strcpy(atts[0], "to");
        strcpy(atts[1], username.toUtf8().data());
	strcpy(atts[2], "type");
	strcpy(atts[3], "subscribe");
	strcpy(atts[4], "id");
        strcpy(atts[5], GenerateUniqueID().toUtf8().data());

	entity = new XMLEntity("presence", (const char **)atts);

	// log command
	//_iq_map[atts[5]] = LOGIN;
	
	// send XML command
	char *str = entity->ToString();
        socketAdapter->write(str);
	free(str);
	
	DestroyAttributeMemory(atts, 6);
	delete entity;
}

void
JabberProtocol::SendUnsubscriptionRequest(QString username)
{
	XMLEntity *entity;
	
	char **atts = CreateAttributeMemory(6);
	
	// assemble attributes
	strcpy(atts[0], "to");
        strcpy(atts[1], username.toUtf8().data());
	strcpy(atts[2], "type");
	strcpy(atts[3], "unsubscribe");
	strcpy(atts[4], "id");
        strcpy(atts[5], GenerateUniqueID().toUtf8().data());

	entity = new XMLEntity("presence", (const char **)atts);

	// send XML command
	char *str = entity->ToString();
        socketAdapter->write(str);
	free(str);
	
	DestroyAttributeMemory(atts, 6);
	delete entity;
}

void
JabberProtocol::AddToRoster(UserID *new_user)
{
	/*
	<iq from='juliet@example.com/balcony' type='set' id='roster_2'>
     <query xmlns='jabber:iq:roster'>
       <item jid='nurse@example.com'
             name='Nurse'>
         <group>Servants</group>
       </item>
     </query>
   </iq>
   */
   
        QString xml = "<iq type='set'>"
		"<query xmlns='jabber:iq:roster'>"
		"<item jid='";
        xml.append(new_user->Handle());
        xml.append("' name='");
        xml.append(new_user->FriendlyName());
        xml.append("' subscription='to'>");
	
	if (new_user->UserType() == UserID::CONFERENCE)
	{
                xml.append("<group>#Conference</group>");
	}
	
        xml.append("</item></query></iq>");
	
	if (new_user->UserType() == UserID::CONFERENCE && _storage_supported)
		SaveConference(new_user);
	else
                socketAdapter->write(xml.toUtf8());
   
}

void
JabberProtocol::RemoveFromRoster(UserID *removed_user)
{
	XMLEntity *entity, *entity_query, *entity_item;

	char **atts       = CreateAttributeMemory(4);
	char **atts_query = CreateAttributeMemory(2);
	char **atts_item  = CreateAttributeMemory(6);
	
	// assemble attributes
	strcpy(atts[0], "type");
	strcpy(atts[1], "set");
	strcpy(atts[2], "id");
        strcpy(atts[3], GenerateUniqueID().toUtf8().data());

	strcpy(atts_query[0], "xmlns");
	strcpy(atts_query[1], "jabber:iq:roster");

	strcpy(atts_item[0], "jid");
        strcpy(atts_item[1], removed_user->Handle().toUtf8().data());
	strcpy(atts_item[2], "name");
        strcpy(atts_item[3], removed_user->FriendlyName().toUtf8().data());
	strcpy(atts_item[4], "subscription");
	strcpy(atts_item[5], "remove");

	entity = new XMLEntity("iq", (const char **)atts);
	entity_query = new XMLEntity("query", (const char **)atts_query);
	entity_item = new XMLEntity("item", (const char **)atts_item);

	entity_query->AddChild(entity_item);
	entity->AddChild(entity_query);

	// log command
	//_iq_map[atts[3]] = ROSTER;

	// send XML command
	char *str = entity->ToString();
        socketAdapter->write(str);
	free(str);
	
	DestroyAttributeMemory(atts, 4);
	DestroyAttributeMemory(atts_query, 2);
	DestroyAttributeMemory(atts_item, 6);
	
	delete entity;
}

void
JabberProtocol::ProcessUserPresence(UserID *user, XMLEntity *entity)
{
	char buffer[4096];
	
	// get best asker name
        QString asker;
					
	if (user && user->FriendlyName().size() > 0) {
		// they have a friendly name
                asker = user->FriendlyName();
	} else if (entity->Attribute("from")) {
		// they have a JID
		asker = entity->Attribute("from");
	} else {
		// they have no identity (illegal case)
		asker = "<unknown>";
	}
	
	// get presence
        QString availability;

	if (entity->Attribute("type")) {
		availability = entity->Attribute("type");
	} else {
		availability = "available";
	}
	
	
	
	// reflect presence
        if (user && !strcasecmp(availability.toUtf8().data(), "unavailable"))
	{
		if (entity->Child("delay"))
			return;
		
		if (user->SubscriptionStatus() == "none")
		{
			user->SetOnlineStatus(UserID::UNKNOWN);
		}
		else
		{	
			if (user->OnlineStatus() == UserID::ONLINE)
			{
				user->SetOnlineStatus(UserID::OFFLINE);
			}
		}
		
                fprintf(stderr, "User %s is unavailable.\n", user->JabberHandle());
	}
        else if (user && !strcasecmp(availability.toUtf8().data(), "available"))
	{
		user->SetOnlineStatus(UserID::ONLINE);
		
                fprintf(stderr, "User %s is available '%s'.\n", user->JabberHandle(),
                                                                                                                                user->SubscriptionStatus());
	}
        else if (user && !strcasecmp(availability.toUtf8().data(), "unsubscribe"))
	{
		if (user->SubscriptionStatus() == "none")
		{
			user->SetOnlineStatus(UserID::UNKNOWN);
		}
		
                sprintf(buffer, "User %s is unsubscribed from you.\n", user->JabberHandle());
		fprintf(stderr, buffer);
                //ModalAlertFactory::NonModalAlert(buffer, "I feel so unloved.");
	}
        else if (user && !strcasecmp(availability.toUtf8().data(), "unsubscribed"))
	{
		if (entity->Attribute("subscription"))
		{
			if (!strcasecmp(entity->Attribute("subscription"), "from"))
				user->SetOnlineStatus(UserID::OFFLINE);
			else if (!strcasecmp(entity->Attribute("subscription"), "none"))
				user->SetOnlineStatus(UserID::UNKNOWN);
		} else {
			if (user->SubscriptionStatus() == "none")
				user->SetOnlineStatus(UserID::UNKNOWN);
			else 
				user->SetOnlineStatus(UserID::OFFLINE);
		}
			
                sprintf(buffer, "User %s deny request or cancel subscription", user->JabberHandle());
		fprintf(stderr, buffer);
	}
        else if (user && !strcasecmp(availability.toUtf8().data(), "subscribed"))
	{
		// http://tools.ietf.org/html/rfc3921
		// 8.2.  User Subscribes to Contact, paragraph 8
		
		if (user->SubscriptionStatus()=="none" || user->SubscriptionStatus()=="from")
			SendSubscriptionRequest(user->JabberHandle());
			
		user->SetOnlineStatus(UserID::ONLINE);
		
                sprintf(buffer, "Your subscription request was accepted by %s!", user->JabberHandle());
                //ModalAlertFactory::Alert(buffer, "Hooray!");
	}
        else if (user && !strcasecmp(availability.toUtf8().data(), "subscribe"))
	{
		
                sprintf(buffer, "User %s wants to subscribe to you.\n", user->JabberHandle());
		fprintf(stderr, buffer);

		// query for presence authorization (for users)
                int answer = 0;
				
                //answer = ModalAlertFactory::Alert(buffer, "Deny", "Grant!");

		// send back the response
		if (answer == 1) {
			// presence is granted
                        AcceptPresence(QString(entity->Attribute("from")));
			SendSubscriptionRequest(user->JabberHandle());
		} else if (answer == 0) {
			// presence is denied
                        RejectPresence(QString(entity->Attribute("from")));
		}
	}
        else if (!strcasecmp(availability.toUtf8().data(), "error"))
	{
		if (entity->Child("error") && entity->Child("error")->Child("text"))
		{
			string username(entity->Attribute("from"));
                        sprintf(buffer, "Presence error from %s:\n\n%s.\n", username,
				entity->Child("error")->Child("text")->Data());
			fprintf(stderr, buffer);
			
                        //ModalAlertFactory::NonModalAlert(buffer, "Sad");
		}
	}

        if (user && !strcasecmp(availability.toUtf8().data(), "available"))
	{
		if (entity->Child("show") && entity->Child("show")->Data()) {
                        user->SetExactOnlineStatus(QString(entity->Child("show")->Data()));
		} else {
			user->SetExactOnlineStatus("chat");
			
		}	

		if (entity->Child("status") && entity->Child("status")->Data()) {
                        user->SetMoreExactOnlineStatus(QString(entity->Child("status")->Data()));
		} else
			user->SetMoreExactOnlineStatus("");
			
	}
	

}

void
JabberProtocol::ParseStorage(XMLEntity *storage)
{
        //JRoster::Instance()->Lock();
	
	for (int i=0; i<storage->CountChildren(); ++i)
	{
                UserID user(QString(storage->Child(i)->Attribute("jid")));
                UserID *roster_user = NULL;//JRoster::Instance()->FindUser(&user);
		
		if (roster_user)
		{
                        fprintf(stderr, "Cnference %s already in roster. Remove it first.\n", user.JabberHandle());
		}
		else
		{
                        fprintf(stderr, "Added conference %s to roster.\n", user.JabberHandle());
                        roster_user = new UserID(user.JabberHandle());
                        //JRoster::Instance()->AddRosterUser(roster_user);
		}
		
                roster_user->SetFriendlyName(QString(storage->Child(i)->Attribute("name")));
		roster_user->SetOnlineStatus(UserID::CONF_STATUS);
		roster_user->SetUsertype(UserID::CONFERENCE);
		if (storage->Child(i)->Child("nick"))
			roster_user->SetRoomNick(storage->Child(i)->Child("nick")->Data());
			
	}
	
        //JRoster::Instance()->Unlock();
	
        //JRoster::Instance()->RefreshRoster();
}

void
JabberProtocol::ParseRosterList(XMLEntity *iq_roster_entity)
{
	XMLEntity *entity = iq_roster_entity;
	
	// go one level deep to query
	if (entity->Child("query")) {
		entity = entity->Child("query");
	} else {
		return;
	}
	
        //JRoster::Instance()->Lock();
	
	for (int i=0; i<entity->CountChildren(); ++i)
	{

		// handle the item child
		if (!strcasecmp(entity->Child(i)->Name(), "item"))
		{
			if (!entity->Child(i)->Attribute("jid"))
			{
				continue;
			}
			
			

			// make a user
                        UserID user(QString(entity->Child(i)->Attribute("jid")));

			// set subscription status
			if (entity->Child(i)->Attribute("subscription"))
			{
                                //fprintf(stderr, "User %s subscription status: %s.\n", user.JabberHandle(),
				//	entity->Child(i)->Attribute("subscription"));
					
                                user.SetSubscriptionStatus(QString(entity->Child(i)->Attribute("subscription")));
			}
			
			
			// set user type
			if (entity->Child(i)->Child("group") &&
				!strcasecmp(entity->Child(i)->Child("group")->Data(), "#Conference"))
			{
				user.SetUsertype(UserID::CONFERENCE);
				user.SetOnlineStatus(UserID::CONF_STATUS);
                                fprintf(stderr, "Roster item %s (conference).\n", user.JabberHandle());
			}
			else
			{
			
				user.SetUsertype(UserID::JABBER);
                                fprintf(stderr, "Roster item %s.\n", user.JabberHandle());
			}

			// set friendly name
			if (entity->Child(i)->Attribute("name")) {
                                user.SetFriendlyName(QString(entity->Child(i)->Attribute("name")));
			}
			
			

                        UserID *roster_user = NULL;//JRoster::Instance()->FindUser(&user);
			
			if (roster_user) 
			{
				// Roster item updating never changes Online Status
				// Online Status can only be changed by <presence> messages
			
                                if (!user.SubscriptionStatus().isEmpty())
				{
					if (user.SubscriptionStatus() == "remove")
					{
                                                //JRoster::Instance()->RemoveUser(roster_user);
												
						fprintf(stderr, "User %s was removed from roster.\n",
                                                        roster_user->JabberHandle());
							
						continue;
					}
					else
					{
			
						if (user.SubscriptionStatus() == "from" && 
							(roster_user->OnlineStatus() == UserID::UNKNOWN))
						{
							roster_user->SetOnlineStatus(UserID::OFFLINE);
						}
						else if (user.SubscriptionStatus() == "none" &&
							roster_user->OnlineStatus() == UserID::OFFLINE)
						{
							roster_user->SetOnlineStatus(UserID::UNKNOWN);
						}
						
						fprintf(stderr, "User %s in roster just updated subscription from '%s' to '%s'.\n",
                                                        roster_user->JabberHandle(),
                                                        user.SubscriptionStatus(),
                                                        roster_user->SubscriptionStatus());
					
						roster_user->SetSubscriptionStatus(user.SubscriptionStatus());
					}
				}
				
                                if (!user.FriendlyName().isEmpty())
					roster_user->SetFriendlyName(user.FriendlyName());

			}
			else
			{
				// Handle Roster Item from Server that is not in our Roster List
				// Determine OFFLINE or UNKNOWN nodes to initial placing
				
				if (user.UserType() == UserID::JABBER)
				{
					if (user.SubscriptionStatus() == "none")
						user.SetOnlineStatus(UserID::UNKNOWN);
					else 
						user.SetOnlineStatus(UserID::OFFLINE);
				}
					
                                roster_user = new UserID(QString(entity->Child(i)->Attribute("jid")));
				
				roster_user->SetSubscriptionStatus(user.SubscriptionStatus());
				roster_user->SetFriendlyName(user.FriendlyName());
				roster_user->SetOnlineStatus(user.OnlineStatus());
				roster_user->SetUsertype(user.UserType());
								
                                //JRoster::Instance()->AddRosterUser(roster_user);
				
				fprintf(stderr, "User %s was added to roster subscription='%s'.\n",
                                        roster_user->JabberHandle(), roster_user->SubscriptionStatus());
			}
			
			
			
		}
	}
	
        //JRoster::Instance()->Unlock();

        //JRoster::Instance()->RefreshRoster();

	
}

void
JabberProtocol::ReceiveData(QVariant *msg)
{
        QByteArray packet;
        QString msgData;
	
        msg->clear();//MakeEmpty();
	
	bool found_stream_start = false;
	bool found_stream_end = false;
	
	do 
	{
                QString data;
                int length;
		
                packet.clear();//MakeEmpty();
                packet = socketAdapter->readAll();//receiveData(&packet);
	
                //packet.FindString("data", &data);
                //packet.FindInt32("length", &length);

                //if (data.FindFirst("<stream:stream") >= 0)
                //	found_stream_start = true;
			
                //if (data.FindFirst("</stream:stream") >= 0)
                //	found_stream_end = true;
			
                msgData.append(data);
		
        } while (FXMLCheck(msgData.toUtf8().data()) == NULL &&
				!found_stream_start && !found_stream_end);
	
	// TODO: handle XML head more accurately
	
        //msgData.RemoveFirst("<?xml version='1.0'?>").Append("</dengon>").Prepend("<dengon>");
					
        //msg->AddInt32("length", msgData.Length());
        //msg->AddString("data", msgData);
	
}

void 
JabberProtocol::RequestRoomOptions(QString room)
{
        QString xml = "<iq from='";
        xml.append(jid);
        xml.append("' id='request_room_info' to='");
        xml.append(room);
        xml.append("' type='get'><query xmlns='http://jabber.org/protocol/muc#owner'/></iq>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::ReceivedMessageHandler(QVariant *msg)
{
        QString data;
        //msg->FindString("data", &data);
        //int length = msg->FindInt32("length");
	
#ifdef DEBUG

                fprintf(stderr, "\nDATA received %i: %s\n", (int)data.length(), data.toUtf8().data());

#endif

	Reset();
	LockXMLReader();
        FeedData(data.toUtf8().data(), data.length());
	UnlockXMLReader();

}



void
JabberProtocol::SendMessage(QString to, QString text)
{
	/*
        QString xml = "<message type='chat' to='";
	xml = xml.Append(to);
	xml << "'><body>";
	xml = xml.Append(text);
	xml << "</body></message>";
	
	socketAdapter->SendData(xml);
	*/
	
	XMLEntity   *entity;
	char **atts = CreateAttributeMemory(4);

	// assemble attributes;
	strcpy(atts[0], "to");
        strcpy(atts[1], to.toUtf8().data());
	strcpy(atts[2], "type");
	strcpy(atts[3], "chat");
	
	// construct XML tagset
	entity = new XMLEntity("message", (const char **)atts);

        entity->AddChild("body", NULL, text.toUtf8().data());
        //entity->AddChild("thread", NULL, thread_id);

	// send XML command
	char *str = entity->ToString();
        socketAdapter->write(str);
	free(str);

	DestroyAttributeMemory(atts, 4);
	
	delete entity;
}

void
JabberProtocol::SendGroupchatMessage(QString to, QString text)
{
	/*
        QString xml = "<message type='groupchat' to='";
	xml = xml.Append(to);
	xml << "'><body>";
	xml = xml.Append(text);
	xml << "</body></message>";
	
	socketAdapter->SendData(xml);
	*/
	XMLEntity   *entity;
	char **atts = CreateAttributeMemory(4);

	// assemble attributes;
	strcpy(atts[0], "to");
        strcpy(atts[1], to.toUtf8().data());
	strcpy(atts[2], "type");
	strcpy(atts[3], "groupchat");
	
	// construct XML tagset
	entity = new XMLEntity("message", (const char **)atts);

        entity->AddChild("body", NULL, text.toUtf8().data());

	// send XML command
	char *str = entity->ToString();
        socketAdapter->write(str);
	free(str);

	DestroyAttributeMemory(atts, 4);
	
	delete entity;
}

void 
JabberProtocol::RequestInfo()
{
        QString xml;
        xml.append("<iq type='get' to='gmail.com'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>");
	
       socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::RequestRoster() 
{
        logged->acquire();
        logged->release();
		
        QString xml;
	
        xml.append("<iq id='roster_2' from='");
        xml.append(jid);
        xml.append("' type='get'><query xmlns='jabber:iq:roster'/></iq>");
		
        socketAdapter->write(xml.toUtf8());
	
}

void
JabberProtocol::SendStorageRequest(QString tag, QString ns)
{
        QString xml = "<iq type='get' id='storage_request'>"
		"<query xmlns='jabber:iq:private'><";
         xml.append(tag);
         xml.append(" xmlns='");
         xml.append(ns);
         xml.append("'/></query></iq>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::SendMUCConferenceRequest(QString conference)
{
        QString xml = "<iq from='";
        xml.append(jid);
        xml.append("' id='disco1' to='");
        xml.append(conference);
        xml.append("' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::SendMUCRoomRequest(QString room)
{
	/*
		<iq from='hag66@shakespeare.lit/pda'
�  �  	id='disco3'
�  �  	to='darkcave@chat.shakespeare.lit'
�  �  	type='get'>
�  		<query xmlns='http://jabber.org/protocol/disco#info'/>
		</iq>
	*/
	
        QString xml = "<iq from='";
        xml.append(jid);
        xml.append("' id='disco3' to='");
        xml.append(room);
        xml.append("' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::InitSession() 
{
        QString xml = "<stream:stream to='";
        xml.append(domain);
        xml.append("' "
		"xmlns='jabber:client' "
		"xmlns:stream='http://etherx.jabber.org/streams' "
                "version='1.0'>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::Session()
{
        QString xml = "<iq to='";
        xml.append(domain);
        xml.append("' type='set' id='sess_1'>"
                                "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>");
	
        socketAdapter->write(xml.toUtf8());
}

void
JabberProtocol::Bind() 
{
        QString xml = "<iq type='set' id='bind_0'>"
  		"<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
    	"<resource>haiku</resource>"
    	"</bind>"
		"</iq>";

        socketAdapter->write(xml.toUtf8());
}

bool
JabberProtocol::BeginSession() 
{
	if (host == "" || domain == "" || user == "" || pass == "" || port <= 0)
		return false;

        //socketAdapter->Create();
        socketAdapter->connectToHostEncrypted(host, port, QSslSocket::ReadWrite);
	
	if (secure)
            socketAdapter->connectToHostEncrypted(host, port, QSslSocket::ReadWrite);
        else
            socketAdapter->connectToHost(host, port, QSslSocket::ReadWrite);
		
        if (socketAdapter->isOpen())
	{
		InitSession();
		return true;
	}

	return false;
}

// SASL PLAIN

bool
JabberProtocol::Authorize()
{
        QString credentials = user + "@" + domain + "\0" + user + "\0" + pass;
        QString xml = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>";

        xml.append(credentials.toUtf8().toBase64());
        xml.append("</auth>");
					
        socketAdapter->write(xml.toUtf8());
	
	return false;
}
